//=======================================================================
//		Copyright (c) 2011-2012 by MicroStrategy, Inc.  This copyrighted material
//		is the confidential, unpublished property of MicroStrategy.  This
//		copyright notice and any other copyright notices included in machine
//		readable copies must be reproduced on all authorized copies.
//
//		File:	Common.js
//
//		Date:	Monday, May 16, 2011
//
//		Desc:	Common JScript data and methods.
//=======================================================================

// EnumDSSFunctionType
var DssFunctionTypeReserved = 0;
var DssFunctionTypeGeneric = 1;
var DssFunctionTypeSimple = 2;
var DssFunctionTypeAggregation = 3;
var DssFunctionTypeRelative = 4;
var DssFunctionTypeIndex = 5;
var DssFunctionTypeQualify = 6;
var DssFunctionTypeVVToS = 7;

// EnumDSSFunctionSQLType
var DssFunctionSQLTypeReserved = 0;
var DssFunctionSQLTypeAggregation = 1;
var DssFunctionSQLTypeLogic = 2;
var DssFunctionSQLTypeArithmetic = 3;
var DssFunctionSQLTypeComparison = 4;
var DssFunctionSQLTypeRelative = 5;

// function modes
var fmSimple = 0;
var fmTable = 1;
var fmAgg = 2;
var aModeTypes = new Array("Simple", "Table", "Aggregation");
var aDSSFuncTypes = new Array(DssFunctionTypeSimple, DssFunctionTypeGeneric, DssFunctionTypeAggregation);
var aSQLTypes = new Array(DssFunctionSQLTypeArithmetic, DssFunctionSQLTypeRelative, DssFunctionSQLTypeAggregation);

// function plug-in types
var ftSimple = 0;
var ftGeneric = 1;
var ftAggregation = 2;
var ftNNSimple = 3;
var ftNNGeneric = 4;
var aObjAbbrs = new Array("SIM", "GEN", "AGG", "NNSIM", "NNGEN");
var aPropCnts = new Array(0, 0, 0, 0, 0);
var aFPCounts = new Array(0, 0, 0, 0, 0);

// parameter types
var paScalar = 0;
var paVector = 1;
var aParamTypes = new Array("Scalar", "Vector");
var aParamTypesPDS = new Array("1", "2");

// data types
var daNumeric = 0;
var daDate = 1;
var daString = 2;
var aDataDssTypes = new Array("DssDataTypeDouble", "DssDataTypeDate", "DssDataTypeVarChar");
var aDataDispTypes = new Array("numeric", "date", "string");
var aDataCTypes = new Array("double", "DATE_STR", "CHSTR");
var aDataVTs = new Array("VT_R8", "VT_DISPATCH", "VT_BSTR");
var aDataVals = new Array("dblVal", "date", "bstrVal");
var aDataNumeric = new Array(true, false, false);
var aDataTypesPDS = new Array("6", "14", "9");

// i/o types
var ioInput = 0;
var ioOutput = 1;
var aIOTypes = new Array("input", "output");
var aIOTypesPDS = new Array("true", "false");

// property types
var prByte = 0;
var prShort = 1;
var prLong = 2;
var prFloat = 3;
var prDouble = 4;
var prBstr = 5;
var prBool = 6;
var prDate = 7;
var aPropTypes = new Array("byte", "short", "long", "float", "double", "BSTR", "bool", "DATE");
var aPropDispTypes = new Array("byte", "short", "long", "float", "double", "string", "boolean", "date");
var aPropVTs = new Array("VT_UI1", "VT_I2", "VT_I4", "VT_R4", "VT_R8", "VT_BSTR", "VT_BOOL", "VT_DATE");
var aPropVals = new Array("bVal", "iVal", "lVal", "fltVal", "dblVal", "bstrVal", "boolVal", "date");

// threading model types
var tmSingle = 0;
var tmApartment = 1;
var tmBoth = 2;
var tmFree = 3;

// interface types
var ifDual = 0;
var ifCustom = 1;

// aggregation types
var agYes = 0;
var agNo = 1;
var agOnly = 2;

// operation types
var opAdd = 0;
var opEdit = 1;

// array dimension types
var adNone = 0;
var ad1Dim = 1;
var ad2Dim = 2;

// boolean values
var boFalse = 0;
var boTrue = 1;
var aBoolVals = new Array("false", "true");

// cell selection values
var CELL_SELECT = " >>";
var CELL_DESELECT = "";

// invalid characters
var INVALID_NAME_CHARS = "/.;,:|\\{}[]+=-!@#$%^&*() ~?><:'\"";
var INVALID_FIRST_CHARS = "0123456789";
var INVALID_LIBDESC_CHARS = "\"\\"
var LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz";
var UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

// class suffixes
var SFX_PACKAGE = "PKG";

// R support
var RSCRIPT_PROPNAME = "RScriptFile";

// error messages
var ERR_MISSING_LIB_DESC = "Please enter a description for the library.";
var ERR_INVALID_LIB_DESC = "The library description cannot contain the following characters: ";
var ERR_INVALID_FUNC_CNT = "Package must contain at least one function.";
var ERR_MISSING_FUNC_DESC = "Please enter a description for the function.";
var ERR_UNIQUE_PARAM_NAME = "Parameter name must be unique within function.";
var ERR_UNIQUE_PROP_NAME = "Property name must be unique within function.";
var ERR_MISSING_DEF_VALUE = "Please enter a default value.";
var ERR_HEX_NOT_SUPPORTED = "Hex representation not supported.";
var ERR_EXP_NOT_SUPPORTED = "The selected type does not support exponential representation."
var ERR_INT_REQUIRED = "The selected type requires an integer value.";
var ERR_INVALID_DEF_VALUE = "Default value is not compatible with selected type.\nValid values: ";
var ERR_INVALID_BYTE = "0..255";
var ERR_INVALID_SHORT = "-32768..32767";
var ERR_INVALID_LONG = "-2,147,483,648..2,147,483,647";
var ERR_INVALID_FLOAT = "approximately -3.4e38..3.4e38";
var ERR_INVALID_DOUBLE = "approximately -1.7e308..1.7e308";
var ERR_INVALID_BOOLEAN = "true or false";
var ERR_INVALID_INPARAM_CNT = "Function must have at least one input parameter.";
var ERR_INVALID_OUTPARAM_CNT = "Function must have exactly one output parameter.";
var ERR_INVALID_AGG_INPARAM_CNT = "Aggregation functions are only allowed one input parameter.";
var ERR_UNKNOWN_MODE_TYPE = "Internal Error: Unknown mode type.";
var ERR_UNKNOWN_FP_TYPE = "Internal Error: Unknown function plug-in type.";
var ERR_UNKNOWN_PROP_TYPE = "Internal Error: Unknown property type.";

// global vars
var eThreadModel = tmBoth;
var eInterface = ifDual;
var eAggregation = agYes;
var bSuppIErrInfo = false;
var bSuppConnPts = false;
var bFreeThrdMrsh = false;
var sIncludes = "";
var g_sSafeRoot;

/*--------------------------------------------------------------------------------------
FUNCTION:   FindOneOf()
INPUTS:     sChars      characters to search for
            sName       string to search
            bRetLoc     indicates what type of index to return
RETURNS:    >= 0        the index of the character in sChars that was found
            -1          no matching characters were found
CREATED:    10/12/07

This function searches the string sName for an occurrence of any of the characters specified
by sChars.  If one is found, the following index is returned, based on the value of bRetLoc:
    * true      0-based index into sName where character was found
    * false     0-based index into sChars of character found

If none of the chars in sChars are found within sName, -1 is returned.
--------------------------------------------------------------------------------------*/
function FindOneOf(sChars, sName, bRetLoc)
{
    var lIndex;
    
    for(var i=0;i<sChars.length;i++)
    {
        lIndex = sName.indexOf(sChars.charAt(i));
        if(lIndex >= 0)
        {
            // found match
            if(bRetLoc)
                // return index into sName
                return lIndex;
            else
                // return index into sChars
                return i;
        }
    }
    
    // no specified characters found
    return -1;
}

/*--------------------------------------------------------------------------------------
FUNCTION:   Truncate()
INPUTS:     sInput          string to be truncated (if too long)
            lMaxLeng        maximum length
RETURNS:    string          original or truncated name
CREATED:    10/16/07

This function truncates the specified string (a name or description) if it exceeds the
specified maximum length, replacing the truncated characters with the string "...".
--------------------------------------------------------------------------------------*/
function Truncate(sInput, lMaxLeng)
{
    var sOutput;
    
    if(sInput.length > lMaxLeng)
    {
        // truncate display version of name
        sOutput = sInput.substr(0, lMaxLeng) + "...";
        return sOutput;
    }
    else
        // no need to truncate
        return sInput;
}

/*--------------------------------------------------------------------------------------
FUNCTION:	AddInclude()
INPUTS:		sNewInclude     new include (format: "<include>")
RETURNS:	none
CREATED:	10/29/07

This function searches the existing list of include files for the specified file name.
If not found, a new #include is concatenated to end of the existing list.
--------------------------------------------------------------------------------------*/
function AddInclude(sNewInclude)
{
	if(sIncludes.indexOf(sNewInclude)==-1)
	{
		// not found...add to string
		sIncludes += "#include " + sNewInclude + "\n";
	}

	return;
}

/*--------------------------------------------------------------------------------------
FUNCTION:	NextSetInput()
INPUTS:		oParam              pointer to current parameter object
            lFuncIndex          current function index
            bGenericFunc        indicates numeric generic function
            lParamIndex         current 1-based input parameter index
            lRepCnt             repeat count value
            bRepeatInput        indicates current input is being repeated
            sAmpersand
            bNoSwitch           no switch statement (one input, one repeat)
            bRFunc              indicates R function
RETURNS:	none
CREATED:	5/14/09

This function builds the 'SetInputParam' code string for the next input parameter.  It
handles scalar/vector inputs and the repeated input(s) option.
--------------------------------------------------------------------------------------*/
function NextSetInput(oParam, lFuncIndex, bGenericFunc, lParamIndex, lRepCnt, bRepeatInput, sAmpersand,  bNoSwitch, bRFunc)
{
	var sBuffer = "";
	var sAllocRepeat = "";
	var sAlloc = "";
	var sIndex = "";
	var sVectorInput = "";
	var sBreak = "";
	var sScalarSzChk = "";
	var bScalar = (oParam.paramType == paScalar);
	
	if(bRepeatInput)
	{
	    // init 'SetInputParam' repeat code strings
	    sAllocRepeat = FP_NGEN_SETINPARAM_ALLOC_REP;
        if(oParam.paramType == paScalar)
            sAllocRepeat = sAllocRepeat.replace(/%s/, "");
        else
            sAllocRepeat = sAllocRepeat.replace(/%s/, " *");
        if(oParam.dataType == daNumeric)
        {
            sAllocRepeat = sAllocRepeat.replace(/%rii/, "");
            if(oParam.paramType == paScalar)
                // array of numbers...use var = arr[n] = 0
                sAllocRepeat = sAllocRepeat.replace(/%riv/, "0");
            else
                // array of pointers...use var = arr[n] = NULL
                sAllocRepeat = sAllocRepeat.replace(/%riv/, "NULL");
        }
        else
        {
            if(oParam.paramType == paScalar)
            {
                // array of strings...use var = arr[n][0] = '\0'
                sAllocRepeat = sAllocRepeat.replace(/%rii/, "[0]");
                sAllocRepeat = sAllocRepeat.replace(/%riv/, "\'\\0\'");
            }
            else
            {
                // array of pointers...use var = arr[n] = NULL
                sAllocRepeat = sAllocRepeat.replace(/%rii/, "");
                sAllocRepeat = sAllocRepeat.replace(/%riv/, "NULL");
            }
        }

        if (bNoSwitch) {
            // special repeat case (1 input, 1 repeat)
            if (bRFunc)
                sIndex = FP_NGEN_SETINPARAM_INDEX_1_1_R;
            else
                sIndex = FP_NGEN_SETINPARAM_INDEX_1_1;
        }
        else {
            if (bRFunc)
                sIndex = FP_NGEN_SETINPARAM_INDEX_R;
            else
                sIndex = FP_NGEN_SETINPARAM_INDEX;
        }
    }

    if (bNoSwitch)
        // single repeated input does not require switch statement
        sBuffer = FP_SWITCH_NOCASE;
    else {
        // use 'case' with specific input parameter index
        sBuffer = FP_SWITCH_CASE;
        sBuffer = sBuffer.replace(/%pi/, lParamIndex);
        sBreak = FP_SWITCH_BREAK;
    }
	
	if (!bScalar)
	    sVectorInput = FP_NGEN_VECTOR_INPUT;
    if (bScalar && bRFunc)
        sScalarSzChk = FP_NGEN_SCALAR_SIZE_CHK;
	
    if (bGenericFunc)
    {
        if (bRFunc) {
            if (bRepeatInput)
                sBuffer += FP_GEN_SETINPARAM_RP_CASE_R;
            else
                sBuffer += FP_GEN_SETINPARAM_CASE_R;
        }
        else if (bScalar)
            sBuffer += FP_GEN_SETINPARAM_SCALAR_CASE;
        else
            sBuffer += FP_GEN_SETINPARAM_VECTOR_CASE;
    }
    else
    {
        if (bRFunc) {
            if (bRepeatInput)
                sBuffer += FP_NNGEN_SETINPUTPARAM_RP_CASE_R;
            else if (bScalar)
                sBuffer += FP_NNGEN_SETINPARAM_SCALAR_CASE_R;
            else
                sBuffer += FP_NNGEN_SETINPARAM_VECTOR_CASE_R;
        }
        else if (bScalar)
            sBuffer += FP_NNGEN_SETINPARAM_SCALAR_CASE;
        else
            sBuffer += FP_NNGEN_SETINPARAM_VECTOR_CASE;
	}
	
	// common replacements
    sBuffer = sBuffer.replace(/%asr/, sAllocRepeat);
    sBuffer = sBuffer.replace(/%fi/g, lFuncIndex + 1);
    sBuffer = sBuffer.replace(/%pn/g, oParam.name);
    sBuffer = sBuffer.replace(/%cty/g, aDataCTypes[oParam.dataType]);
    sBuffer = sBuffer.replace(/%dss/, aDataDssTypes[oParam.dataType]);
    sBuffer = sBuffer.replace(/%amp/, sAmpersand);
    sBuffer = sBuffer.replace(/%ci/g, sIndex);
    sBuffer = sBuffer.replace(/%vi/, sVectorInput);
    sBuffer = sBuffer.replace(/%brk/, sBreak);
    sBuffer = sBuffer.replace(/%ssc/, sScalarSzChk);

	return sBuffer;
}
	
/*--------------------------------------------------------------------------------------
FUNCTION:   ProcessFunc()
INPUTS:     oFunc           reference to function object
            lFuncIndex      0-based function index, based on function type
            lPkgIndex       0-based function index, at package level
RETURNS:    true            no errors during processing
            false           processing terminated due to error
CREATED:    10/29/07

This function process a single function, adding all necessary symbols to the dictionary.
--------------------------------------------------------------------------------------*/
function ProcessFunc(oFunc, lFuncIndex, lPkgIndex)
{
	var sError;
	var sDummy;
	var sBuffer = new String("");
	var sTemp;
	var sObjAbbr;
	var sShortName;
	var sClassName;
	var sInterface;
	var sDictEntry;
	var sDictIndex;
	var sPIndex;
	var sCount;
	var sGetProps = "";
	var sSetProps = "";
	var sPropVars = "";
	var sInitProps = "";
	var sGetInParams = "";
	var sSetInParams = "";
	var sScalarCase = "";
	var sVectorCase = "";
	var sIncrRepCnt;
	var sInParamDecls = "";
	var sInParamInits = "";
	var sGetOutParams = "";
	var sOutParamType;
	var sOutDataType;
	var sOutDataVt;
	var sOutDataVal;
	var sSqlType = "";
	var sGetParamFromVariant = "";
	var sAllocLocalMem = "";
	var sFreeLocalMem = "";
	var sRepCaseIncr;
	var sPropType;
	var sPropVtType;
	var sPropVal;
	var sInParamTypesPds = "";
	var sOutParamTypesPds = "";
	var sInDataTypesPds = "";
	var sOutDataTypesPds = "";
	var sPropsPds = "";
	var sAmpersand;
	var sAsterisks;
	var sRepRange1;
	var sRepRange2;
	var sParamDestruct = "";
	var sPropDestruct = "";
	var sParamClear = "";
	var sInParamCmts = "";
	var sOutParamCmt = "";
	var sPropCmts = "";
	var sSetRVars = "";
	var sGetRVar = "";
	var sStrLen;
	var ePropType;
	var oParam;
	var oProp;
	var bNNSimpleFunc = false;
	var bNNGenericFunc = false;
	var bGenericFunc = false;
	var bAggFunc = false;
	var bNGenVectorOut = false;
	var bNGenVectorInput = false;
	var bAddStdDefault = false;
	var bTerminateSwitch = false;
	var bTerminateForLoop = false;
	var bFirstRepeat = true;
	var bNoSwitch = false;
	var bNumericDT, bScalar, bRepeatInput;
	var eNGenDimCnt;
	var i, lInCnt, lOutCnt, lRepCnt, lTemp;
	var lCaseIndex = 0;
	
	// create classname here (will be needed at end, even if this is not
	// the first function of this type)
	sObjAbbr = aObjAbbrs[oFunc.fpType];
	sShortName = window.external.FindSymbol("safe_root") + sObjAbbr;
	sClassName = "C" + sShortName;
	
	if(!lFuncIndex)
	{
		//--------------------------------------------------
		// first function of this type...process object-based info
		//--------------------------------------------------
		// create interface name
		sInterface = "I" + sShortName;
		
		// generate IID's for function object coclass and interface
	    sBuffer = window.external.FormatGuid(window.external.CreateGuid(), 0);
        window.external.AddSymbol("FUNC" + sObjAbbr + "_COCLASS_IID", sBuffer);
	    sBuffer = window.external.FormatGuid(window.external.CreateGuid(), 0);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_DEFINTERFACE_IID", sBuffer);

		// add symbols for function object names
		window.external.AddSymbol("FUNC" + sObjAbbr + "_SHORTNAME", sShortName);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_CLASSNAME", sClassName);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_COCLASS", sShortName);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_COMTYPE", sShortName + " class");
		window.external.AddSymbol("FUNC" + sObjAbbr + "_HFILE", sShortName + ".h");
		window.external.AddSymbol("FUNC" + sObjAbbr + "_INTERFACE", sInterface);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_VIPROGID", g_sSafeRoot + "." + sShortName);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_PROGID", g_sSafeRoot + "." + sShortName + ".1");
		window.external.AddSymbol("FUNC" + sObjAbbr + "_CLSIDNAME", "CLSID_" + sShortName);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_DEFIIDNAME", "IID_" + sInterface);
	}

	// init function type booleans
	bNNSimpleFunc = (oFunc.fpType == ftNNSimple);
	bGenericFunc = (oFunc.fpType == ftGeneric);
	bNNGenericFunc = (oFunc.fpType == ftNNGeneric);
	bAggFunc = (oFunc.fpType == ftAggregation);

	// init function object-based dictionary index and count strings
	// (dictionary index is one-based)
	lTemp = lFuncIndex + 1;
	sCount = lTemp.toString(10);
	sDictIndex = sCount;

	// process function-based info
	window.external.AddSymbol("FUNC" + sObjAbbr + "_FUNCNAME_" + sDictIndex, oFunc.name);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_FUNCDESC_" + sDictIndex, oFunc.description);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_FPTYPE_" + sDictIndex, oFunc.fpType);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_MODE_" + sDictIndex, oFunc.mode);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_CPPFILE_" + sDictIndex, oFunc.name + ".cpp");
	window.external.AddSymbol("FUNC" + sObjAbbr + "_INDEX_" + sDictIndex, sCount);

	if(bNNSimpleFunc || bNNGenericFunc)
	{
		// add 'stdio' and 'time' header files...needed for date conversions
		AddInclude("<stdio.h>");
		AddInclude("<time.h>");
	}
	
	// R Support
	if(oFunc.bRFunc)
	    // add symbol for R function flag
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_RFUNC_" + sDictIndex, "TRUE");

	//----------------------------------------------------------------------------
	// process function parameters
	//----------------------------------------------------------------------------
	// add symbol for parameter count
	window.external.AddSymbol("FUNC" + sObjAbbr + "_PARAMCNT_" + sDictIndex, oFunc.params.length);

    // check if not single repeated input parameter
    if((oFunc.params.length != 2) || (oFunc.lRepCnt == 0))
    {
        // 'SetInputParam' needs switch on input params
        if(oFunc.lRepCnt > 0)
            sSetInParams = FP_SWITCH_STMT_START_REP;
        else
            sSetInParams = FP_SWITCH_STMT_START;
        bTerminateSwitch = true;
	    
        // need standard default if not repeating last parameter
        bAddStdDefault = (oFunc.lRepCnt == 0);
    }
    else
        // single repeated input does not require switch statement
        bNoSwitch = true;
			
	// 'get_ParamInfo' always uses switch
    sGetInParams = FP_SWITCH_STMT_START;
	sGetOutParams = FP_SWITCH_STMT_START;

	// process each parameter
	lInCnt=0;
	lOutCnt = 0;
	lRepCnt = 0;
	for(i=0;i<oFunc.params.length;i++)
	{
		// get access to the parameter
		oParam = oFunc.params[i];
		
	    // initialize
	    sPIndex = i.toString(10);
	    sAsterisks = "";
	    sIncrRepCnt = "";
	    bNumericDT = (oParam.dataType == daNumeric);
	    bScalar = (oParam.paramType == paScalar);
	    bRepeatInput = ((i >= oFunc.params.length - oFunc.lRepCnt - (1 - lOutCnt)));
	    eNGenDimCnt = adNone;
	    
		// add symbols for parameter info
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_PARAMNAME_" + sDictIndex + "_" + sPIndex, oParam.name);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_PARAMTYPE_" + sDictIndex + "_" + sPIndex, oParam.paramType);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_DATATYPE_" + sDictIndex + "_" + sPIndex, oParam.dataType);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_IOTYPE_" + sDictIndex + "_" + sPIndex, oParam.ioType);

		// check parameter type
		if(oParam.ioType == ioInput)
		{
			lInCnt++;

			// create next 'get_ParamInfo' code string, and add to previous strings
            sBuffer = FP_SWITCH_CASE;
            sBuffer += FP_GETPARAMINFO_CASE;
            sBuffer = sBuffer.replace(/%pi/, lInCnt);
			sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			sBuffer = sBuffer.replace(/%pt/, aParamTypes[oParam.paramType]);
			sGetInParams += sBuffer;

			// create next parameter type code string for pds, and add to previous strings
			sBuffer = FP_PARAMTYPE_PDS;
			sBuffer = sBuffer.replace(/%i/, lInCnt);
			sBuffer = sBuffer.replace(/%pn/, oParam.name);
			sBuffer = sBuffer.replace(/%i/, aParamTypesPDS[oParam.paramType]);
			sBuffer = sBuffer.replace(/%s/, aIOTypesPDS[oParam.ioType]);
			sInParamTypesPds += sBuffer;

			// create next data type code string for pds, and add to previous strings
			sBuffer = FP_DATATYPE_PDS;
			sBuffer = sBuffer.replace(/%i/, lInCnt);
			sBuffer = sBuffer.replace(/%i/, aDataTypesPDS[oParam.dataType]);
			sBuffer = sBuffer.replace(/%s/, aIOTypesPDS[oParam.ioType]);
			sInDataTypesPds += sBuffer;

			// determine if ampersand required with variable reference
			if(bNumericDT)
				sAmpersand = "&";
			else
				sAmpersand = "";

			// create fp type-specific code strings and comments
			switch(oFunc.fpType)
			{
			case ftSimple:
			case ftNNSimple:
			case ftAggregation:
			    {
			        // initialize repeat count ranges when appropriate
			        if (bRepeatInput) {
			            lTemp = lInCnt - 1 + oFunc.lRepCnt;
			            sRepRange1 = "," + lTemp.toString(10);
			            lTemp += oFunc.lRepCnt;
			            sRepRange1 += "," + lTemp.toString(10) + ",...";
			            sRepRange2 = "[0,1,2,...]";
			        }
			        else {
			            // no-repeat Simple/NNSimple, or Aggregation (repeats not allowed)
			            sRepRange1 = "";
			            sRepRange2 = "";
			        }

			        // create parameter comment
			        sBuffer = FP_INPARAM_COMMENT;
			        if (bAggFunc)
			        // input always a vector...force index to 'r'
			            sBuffer = sBuffer.replace(/%pi/, "r");
			        else
			            sBuffer = sBuffer.replace(/%pi/, lInCnt - 1);
			        sBuffer = sBuffer.replace(/%pn/, oParam.name);
			        sBuffer = sBuffer.replace(/%pt/, aParamTypes[oParam.paramType]);
			        sBuffer = sBuffer.replace(/%dsp/, aDataDispTypes[oParam.dataType]);
			        sBuffer = sBuffer.replace(/%rc1/, sRepRange1);
			        sBuffer = sBuffer.replace(/%rc2/, sRepRange2);
			        sInParamCmts += sBuffer;
			        if (bRepeatInput)
			            sInParamCmts += FP_REPEAT_CMT;

			        if (bNNSimpleFunc && !oFunc.bRFunc) {
			            // create next 'InParamDecl' code string, and add to previous strings
			            if (bRepeatInput)
			                sBuffer = FP_INPARAMDECLS_REP;
			            else
			                sBuffer = FP_INPARAMDECLS;
			            sBuffer = sBuffer.replace(/%cty/, aDataCTypes[oParam.dataType]);
			            sBuffer = sBuffer.replace(/%pn/, oParam.name);
			            sInParamDecls += sBuffer;

			            // create next 'GetParamFromVariant' code string, and add to previous strings
			            if (bRepeatInput) {
			                if (bFirstRepeat) {
			                    // add start of for-loop
			                    sGetParamFromVariant += FP_GPFV_REP_FOR_START;
			                    bTerminateForLoop = true;

			                    // reset flag
			                    bFirstRepeat = false;
			                }

			                sBuffer = FP_GPFV_REP_CASE;
			                sBuffer = sBuffer.replace(/%rcn/, lCaseIndex++);

			                // check if last input
			                if (i == oFunc.params.length - (2 - lOutCnt))
			                    sRepCaseIncr = "\t\t\t\tj++;\r\n";
			                else
			                    sRepCaseIncr = "";
			            }
			            else
			                sBuffer = FP_GETPARAMFROMVARIANT;
			            sBuffer = sBuffer.replace(/%pi/, lInCnt - 1);
			            sBuffer = sBuffer.replace(/%cty/, aDataCTypes[oParam.dataType]);
			            sBuffer = sBuffer.replace(/%dss/, aDataDssTypes[oParam.dataType]);
			            sBuffer = sBuffer.replace(/%amp/, sAmpersand);
			            sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			            sBuffer = sBuffer.replace(/%rci/, sRepCaseIncr);
			            sGetParamFromVariant += sBuffer;

			            // create next alloc/free local memory code string, and add to previous strings
			            if (bRepeatInput) {
			                sBuffer = FP_ALLOCLOCALMEM_REP;
			                sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			                sBuffer = sBuffer.replace(/%cty/, aDataCTypes[oParam.dataType]);
			                sAllocLocalMem += sBuffer;

			                sBuffer = FP_FREELOCALMEM_REP;
			                sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			                sFreeLocalMem += sBuffer;
			            }
			        }

			        break;
			    }
			case ftGeneric:
			case ftNNGeneric:
			    {
			        // create parameter comment
			        if (oFunc.bRFunc)
			            sBuffer = FP_NGEN_INPARAM_COMMENT_R;
			        else
			            sBuffer = FP_NGEN_INPARAM_COMMENT;
			        sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			        sBuffer = sBuffer.replace(/%pn/, oParam.name);
			        if (bScalar) {
			            if (bRepeatInput) {
			                sBuffer = sBuffer.replace(/%s/, "[c]");
			                sAsterisks = "*";
			                eNGenDimCnt = ad1Dim;
			            }
			            else
			                sBuffer = sBuffer.replace(/%s/, "");
			        }
			        else {
			            // set flag to indicate at least one vector input
			            bNGenVectorInput = true;

			            if (bRepeatInput) {
			                sBuffer = sBuffer.replace(/%s/, "[c][r]");
			                sAsterisks = "**";
			                eNGenDimCnt = ad2Dim;
			            }
			            else {
			                sBuffer = sBuffer.replace(/%s/, "[r]");
			                sAsterisks = "*";
			                eNGenDimCnt = ad1Dim;
			            }
			        }
			        sBuffer = sBuffer.replace(/%pt/, aParamTypes[oParam.paramType]);
			        sBuffer = sBuffer.replace(/%dsp/, aDataDispTypes[oParam.dataType]);
			        sInParamCmts += sBuffer;
			        if (bRepeatInput)
			            sInParamCmts += FP_REPEAT_CMT;

			        // create next case statement for 'GetInputParamType', and add to previous strings
			        sBuffer = FP_PARAM_SWITCH_CASE;
			        sBuffer = sBuffer.replace(/%pi/, lInCnt);
			        sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			        sBuffer = sBuffer.replace(/%pn/, oParam.name);
			        if (bScalar)
			            sScalarCase += sBuffer;
			        else
			            sVectorCase += sBuffer;

			        if (!oFunc.bRFunc) {
			            // create next 'InParamDecl' code string, and add to previous strings
			            sBuffer = FP_NGEN_INPARAMDECL;
			            sBuffer = sBuffer.replace(/%cty/, aDataCTypes[oParam.dataType]);
			            sBuffer = sBuffer.replace(/%s/, sAsterisks);
			            sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			            sBuffer = sBuffer.replace(/%pn/, oParam.name);
			            sInParamDecls += sBuffer;

			            // create next 'InParamInit' code string, and add to previous strings
			            if (eNGenDimCnt == adNone) {
			                // input is scalar, no repeat...check type
			                if (bNumericDT)
			                // init to zero
			                    sBuffer = FP_NGEN_INPARAMINIT_NUM;
			                else
			                // init to empty string
			                    sBuffer = FP_NGEN_INPARAMINIT_STR;
			            }
			            else
			            // input is pointer...init to NULL
			                sBuffer = FP_NGEN_INPARAMINIT_PTR;
			            sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			            sBuffer = sBuffer.replace(/%pn/, oParam.name);
			            sInParamInits += sBuffer;
			        }

			        // create next 'SetInputParam' code string, and add to previous strings
			        sSetInParams += NextSetInput(oParam, lFuncIndex, bGenericFunc, lInCnt, oFunc.lRepCnt,
			            bRepeatInput, sAmpersand, bNoSwitch, oFunc.bRFunc);

			        if (!oFunc.bRFunc) {
			            if (eNGenDimCnt != adNone) {
			                // add code to free parameter memory in destructor
			                if (eNGenDimCnt == ad1Dim)
			                    sBuffer = FP_NGEN_PARAMDESTRUCT_1DIM;
			                else // ad2Dim
			                    sBuffer = FP_NGEN_PARAMDESTRUCT_2DIM;

			                sBuffer = sBuffer.replace(/%fi/g, lFuncIndex + 1);
			                sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			                sParamDestruct += sBuffer;

			                // create next 'Clear' code string, and add to previous strings
			                if (eNGenDimCnt == ad1Dim)
			                    sBuffer = FP_NGEN_CLEAR_1DIM;
			                else // ad2Dim
			                    sBuffer = FP_NGEN_CLEAR_2DIM;

			                sBuffer = sBuffer.replace(/%fi/g, lFuncIndex + 1);
			                sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			                sParamClear += sBuffer;
			            }
			            //else...input is scalar, no repeat...no destructor required
			        }

			        break;
			    }
			default:
				{
					// shouldn't get here
					window.alert(ERR_UNKNOWN_FP_TYPE);
					return false;
				}
			}
			
			// R support - create next 'SetRVar' code string, and add to previous strings
			if(oFunc.bRFunc)
			{
			    switch(oFunc.fpType)
			    {
			        case ftSimple:
			            if (bRepeatInput)
			                sBuffer = FP_SIM_SETRVAR_RP;
			            else
			                sBuffer = FP_SIM_SETRVAR;
			            break;
			        case ftNNSimple:
			            if (bRepeatInput)
			                sBuffer = FP_NNSIM_SETRVARSA_RP;
			            else
			                sBuffer = FP_NNSIM_SETRVARV;
			            break;
			        case ftGeneric:
			        case ftNNGeneric:
			            // SetRVar code appears in SetInputParam() (handled by NextSetInput())
			            break;
			        case ftAggregation:
			            sBuffer = FP_AGG_SETRVAR;
			            break;
			    }
			    sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			    sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			    if (bRepeatInput) {
			        sBuffer = sBuffer.replace(/%pi/, lRepCnt);
			        sBuffer = sBuffer.replace(/%dss/, aDataDssTypes[oParam.dataType]);
			        lRepCnt++;
			    }
			    else
			        sBuffer = sBuffer.replace(/%pi/, lInCnt - 1);
			    sSetRVars += sBuffer;
			}
		}
		else  // out parameter
		{
			lOutCnt++;

			// create next 'get_ParamInfo' code string, and add to previous strings
            sBuffer = FP_SWITCH_CASE + FP_GETPARAMINFO_CASE;
            sBuffer = sBuffer.replace(/%pi/, lOutCnt);
	        sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
	        sBuffer = sBuffer.replace(/%pn/g, oParam.name);
	        sBuffer = sBuffer.replace(/%pt/, aParamTypes[oParam.paramType]);
			sGetOutParams += sBuffer;

			// create next parameter type code string for pds, and add to previous strings
			sBuffer = FP_PARAMTYPE_PDS;
	        sBuffer = sBuffer.replace(/%i/, lOutCnt);
	        sBuffer = sBuffer.replace(/%pn/, oParam.name);
	        sBuffer = sBuffer.replace(/%i/, aParamTypesPDS[oParam.paramType]);
			sBuffer = sBuffer.replace(/%s/, aIOTypesPDS[oParam.ioType]);
			sOutParamTypesPds += sBuffer;

			// create next data type code string for pds, and add to previous strings
			sBuffer = FP_DATATYPE_PDS;
			sBuffer = sBuffer.replace(/%i/, lOutCnt);
			sBuffer = sBuffer.replace(/%i/, aDataTypesPDS[oParam.dataType]);
			sBuffer = sBuffer.replace(/%s/, aIOTypesPDS[oParam.ioType]);
			sOutDataTypesPds += sBuffer;

			// create parameter comment
			if(bScalar)
			    sBuffer = FP_SCALAR_OUTPARAM_COMMENT;
			else
			    sBuffer = FP_VECTOR_OUTPARAM_COMMENT;
	        sBuffer = sBuffer.replace(/%pn/, oParam.name);
	        sBuffer = sBuffer.replace(/%pt/, aParamTypes[oParam.paramType]);
		    sBuffer = sBuffer.replace(/%dsp/, aDataDispTypes[oParam.dataType]);
			sOutParamCmt += sBuffer;

            // special case for GEN/NNGEN functions
			if(bGenericFunc || bNNGenericFunc)
			    // record if out parameter type for is vector
				bNGenVectorOut = (oParam.paramType == paVector);
				
		    if(oFunc.bRFunc)
		    {
		        // R support - set string length
		        if(bNumericDT)
		        {
		            sStrLen = "1";
		        }
		        else
		        {
		            if(oParam.dataType == daString)
		                sStrLen = FP_CS_LEN;
		            else
		                sStrLen = FP_DS_LEN;
		        }
			
			    // R support - create 'GetRVar' code string
			    switch(oFunc.fpType)
			    {
			    case ftSimple:
			        sBuffer = FP_SIM_GETRVAR;
			        break;
			    case ftNNSimple:
			        sBuffer = FP_NNSIM_GETRVARNN;
			        break;
			    case ftGeneric:
			        if(bScalar)
			            sBuffer = FP_GEN_SCALAR_GETRVAR;
			        else
			            sBuffer = FP_GEN_VECTOR_GETRVAR;
			        break;
			    case ftNNGeneric:
			        if(bScalar)
			            sBuffer = FP_NNGEN_SCALAR_GETRVARNN;
			        else
			            sBuffer = FP_NNGEN_VECTOR_GETRVARNN;
			        break;
			    case ftAggregation:
			        // same as Simple version
			        sBuffer = FP_SIM_GETRVAR;
			        break;
			    }
			    sBuffer = sBuffer.replace(/%pn/g, oParam.name);
			    sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			    sBuffer = sBuffer.replace(/%pi/, lInCnt);
			    sBuffer = sBuffer.replace(/%sl/, sStrLen);
			    sGetRVar += sBuffer;
		    }

			// get the output parameter and data types
			// NOTE: These lines of code only work as long as the number of output parameters
			// is restricted to exactly one.  Changes will be required if the number of
			// output parameters changes.
			sOutParamType = aParamTypes[oParam.paramType];
			sOutDataType = aDataDssTypes[oParam.dataType];
			sOutDataVt = aDataVTs[oParam.dataType];
			sOutDataVal = aDataVals[oParam.dataType];
		}
	}
	
	// determine SQL type for GEN/NNGEN functions
	if(bGenericFunc || bNNGenericFunc)
	{
	    if(bNGenVectorOut)
	        // vector output
	        sSqlType = "DssFunctionSQLTypeRelative";
	    else
	    {
	        if(bNGenVectorInput)
	            // scalar output with at least one vector input
	            sSqlType = "DssFunctionSQLTypeAggregation";
	        else
	            // all scalar input and output
	            sSqlType = "DssFunctionSQLTypeArithmetic";
	    }
	}
	
	// complete SetInputParam switch statements
	if(bAddStdDefault)
	    sSetInParams += FP_SWITCH_STD_DEFAULT;    
	if(bTerminateSwitch)
	    sSetInParams += FP_SWITCH_STMT_END;
	
	// check if repeat option was used
	if(bTerminateForLoop)
	    // terminate for-loop
	    sGetParamFromVariant += FP_GPFV_REP_FOR_END;
	
	// get_ParamInfo always uses switch
	sGetInParams += FP_SWITCH_STD_DEFAULT + FP_SWITCH_STMT_END;
	sGetOutParams += FP_SWITCH_STD_DEFAULT + FP_SWITCH_STMT_END;
	
	// add symbols for in/out/repeat parameter counts
	sTemp = lInCnt.toString(10);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_INCNT_" + sDictIndex, sTemp);
	sTemp = lOutCnt.toString(10);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTCNT_" + sDictIndex, sTemp);
	sTemp = oFunc.lRepCnt.toString(10);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_REPCNT_" + sDictIndex, sTemp);

	// add symbols for in parameter code strings
	window.external.AddSymbol("FUNC" + sObjAbbr + "_GETINPARAMS_" + sDictIndex, sGetInParams);
	if(sInParamDecls.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_INPARAMDECLS_" + sDictIndex, sInParamDecls);
	if(sInParamInits.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_INPARAMINITS_" + sDictIndex, sInParamInits);
	if(sAllocLocalMem.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_ALLOCLOCALMEM_" + sDictIndex, sAllocLocalMem);
	if(sGetParamFromVariant.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_GETPARAMFROMVARIANT_" + sDictIndex, sGetParamFromVariant);
	if(sFreeLocalMem.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_FREELOCALMEM_" + sDictIndex, sFreeLocalMem);
	if(sScalarCase.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_GETINTYPES_SCALARCASE_" + sDictIndex, sScalarCase);
	if(sVectorCase.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_GETINTYPES_VECTORCASE_" + sDictIndex, sVectorCase);
	if(oFunc.lRepCnt > 0)
	{
	    // repeating inputs specified...calculate index of first repeating input
		window.external.AddSymbol("FUNC" + sObjAbbr + "_FIRSTREP_" + sDictIndex, lInCnt - oFunc.lRepCnt + 1);
		if(!bNoSwitch)
		    window.external.AddSymbol("FUNC" + sObjAbbr + "_SETINPARAMS_REPFLAG_" + sDictIndex, "TRUE");
		//else...code not needed in SetInputParams if no switch statement (inCnt=1,repCnt=1)
	}
	if(sSetInParams.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_SETINPARAMS_" + sDictIndex, sSetInParams);
	if(sInParamCmts.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_INPARAM_COMMENTS_" + sDictIndex, sInParamCmts);
	if(sParamDestruct.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_PARAMDESTRUCT_" + sDictIndex, sParamDestruct);
	if(sParamClear.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_CLEAR_" + sDictIndex, sParamClear);

	// add symbols for out parameter code strings
	window.external.AddSymbol("FUNC" + sObjAbbr + "_GETOUTPARAMS_" + sDictIndex, sGetOutParams);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTPARAM_COMMENT_" + sDictIndex, sOutParamCmt);

	// add symbols for out data and parameter types
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTPARAMTYPE_" + sDictIndex, sOutParamType);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTDATATYPE_" + sDictIndex, sOutDataType);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTDATAVT_" + sDictIndex, sOutDataVt);
	window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTDATAVAL_" + sDictIndex, sOutDataVal);
	if(bNGenVectorOut)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_OUTPARAMVEC_" + sDictIndex, "TRUE");
	
	// add symbol for SQL type
	if(sSqlType.length)
		window.external.AddSymbol("FUNC" + sObjAbbr + "_SQLTYPE_" + sDictIndex, sSqlType);
	
	if(bNGenVectorInput)
	{
	    // add flag to indicate at least one vector input (only set for GEN/NNGEN functions)
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_VECTOR_INPUT", "TRUE");
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_VECTOR_INPUT_" + sDictIndex, "TRUE");
	}
	
	//----------------------------------------------------------------------------
	// process function properties
	//----------------------------------------------------------------------------
	// add symbol for property count
	window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPCNT_" + sDictIndex, oFunc.props.length)

	// process each property
	for(i=0;i<oFunc.props.length;i++)
	{
	    // initialize
	    sPIndex = i.toString(10);
	    
		// get access to property
		oProp = oFunc.props[i];
		ePropType = oProp.propType;
		
		// add symbols for property info
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPNAME_" + sDictIndex + "_" + sPIndex, oProp.name);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPTYPE_" + sDictIndex + "_" + sPIndex, oProp.propType);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_DEFVALUE_" + sDictIndex + "_" + sPIndex, oProp.defValue);
	    window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPDESC_" + sDictIndex + "_" + sPIndex, oProp.description);

		// initialize substitution strings
		sPropType = aPropTypes[ePropType];
		sPropVtType = aPropVTs[ePropType];
		sPropVal = aPropVals[ePropType];

		//----------------------------------------------------------------------------
		// create appropriate code strings, and add to strings for previous properties
		//----------------------------------------------------------------------------
		// property variable code string
		if (oFunc.bRFunc) {
		    if (oProp.name == RSCRIPT_PROPNAME)
		        // use specific R script code string
		        sBuffer = FP_PROPVAR_RSCRIPT;
		    else
		        // no variable code needed
		        sBuffer = "";
		}
		else
		    sBuffer = FP_PROPVAR;
		sBuffer = sBuffer.replace(/%pt/, sPropType);
		sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
		sBuffer = sBuffer.replace(/%pn/, oProp.name);
		sPropVars += sBuffer;

		if (!oFunc.bRFunc || (oProp.name == RSCRIPT_PROPNAME)) {
		    // create property comment
		    sBuffer = FP_PROP_COMMENT;
		    sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
		    sBuffer = sBuffer.replace(/%pn/, oProp.name);
		    sBuffer = sBuffer.replace(/%pt/, sPropType);
		    sBuffer = sBuffer.replace(/%pd/, oProp.description);
		    sPropCmts += sBuffer;
		}

		// 'SetProperty' code string
		if (oFunc.bRFunc) {
		    if (oProp.name == RSCRIPT_PROPNAME)
		        sBuffer = FP_SETPROP_CASE_RSCRIPT;
		    else
		        sBuffer = FP_SETPROP_CASE_R;
		}
		else if (oProp.propType == prBstr)
		    sBuffer = FP_SETPROPBSTR_CASE;
		else if (oProp.propType == prBool)
		    sBuffer = FP_SETPROPBOOL_CASE;
		else
		    sBuffer = FP_SETPROP_CASE;
		sBuffer = sBuffer.replace(/%pi/, i + 1);
		sBuffer = sBuffer.replace(/%pn/g, oProp.name);
		sBuffer = sBuffer.replace(/%pvt/g, sPropVtType);
		sBuffer = sBuffer.replace(/%fi/g, lFuncIndex + 1);
		sBuffer = sBuffer.replace(/%pva/, sPropVal);
		sSetProps += sBuffer;

		// 'GetProperty' code string
		if(oProp.propType == prBstr)
		    sBuffer = FP_GETPROPBSTR_CASE;
		else if (oProp.propType == prDate)
		    sBuffer = FP_GETPROPDATE_CASE;
		else
		    sBuffer = FP_GETPROP_CASE;
		sBuffer = sBuffer.replace(/%pi/, i + 1);
		sBuffer = sBuffer.replace(/%pn/g, oProp.name);
		sBuffer = sBuffer.replace(/%pd/, oProp.description);
		sBuffer = sBuffer.replace(/%pvt/, sPropVtType);
		sBuffer = sBuffer.replace(/%pva/, sPropVal);
		sBuffer = sBuffer.replace(/%pdf/, oProp.defValue);
		sGetProps += sBuffer;

		if (oProp.propType == prBstr) {
		    if (!oFunc.bRFunc || (oProp.name == RSCRIPT_PROPNAME)) {
		        // property initialization code string
			    sBuffer = FP_PROPVAR_INIT_BSTR;
			    sBuffer = sBuffer.replace(/%fi/, lFuncIndex + 1);
			    sBuffer = sBuffer.replace(/%pn/, oProp.name);
			    sInitProps += sBuffer;

			    // property destruction code string
			    if (oFunc.bRFunc)
			        sBuffer = FP_DESTRUCT_STR;
			    else
			        sBuffer = FP_DESTRUCT_BSTR;
			    sBuffer = sBuffer.replace(/%fi/g, lFuncIndex + 1);
			    sBuffer = sBuffer.replace(/%pn/g, oProp.name);
			    sPropDestruct += sBuffer;
            }
		}

		// property code string for pds
	    sBuffer = FP_PROPERTY_PDS;
	    sBuffer = sBuffer.replace(/%s/, oProp.name);
	    sBuffer = sBuffer.replace(/%s/, sPropType);
	    sBuffer = sBuffer.replace(/%s/, oProp.defValue);
	    sBuffer = sBuffer.replace(/%s/, oProp.description);
	    sPropsPds += sBuffer;
		
		// check if last property
		if(i < (oFunc.props.length - 1))
		{
		    // not last property...add n/l
		    sSetProps += FP_NL;
		}
	}
	
	if(oFunc.props.length)
	{
		// at least one property...add code strings to dictionary
		window.external.AddSymbol("FUNC" + sObjAbbr + "_GETPROPS_" + sDictIndex, sGetProps);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_SETPROPS_" + sDictIndex, sSetProps);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPVARS_" + sDictIndex, sPropVars);
		window.external.AddSymbol("FUNC" + sObjAbbr + "_PROP_COMMENTS_" + sDictIndex, sPropCmts);
		if(sInitProps.length)
			window.external.AddSymbol("FUNC" + sObjAbbr + "_INITPROPS_" + sDictIndex, sInitProps);
		if(sPropDestruct.length)
			window.external.AddSymbol("FUNC" + sObjAbbr + "_PROPDESTRUCT_" + sDictIndex, sPropDestruct);
	}
	//else...code strings empty

    // R support
    if (sSetRVars != "")
        window.external.AddSymbol("FUNC" + sObjAbbr + "_SETRVARS_" + sDictIndex, sSetRVars);
    if (sGetRVar != "")
        window.external.AddSymbol("FUNC" + sObjAbbr + "_GETRVAR_" + sDictIndex, sGetRVar);

	//----------------------------------------------------------------------------
	// add package-based entries to dictionary
	//----------------------------------------------------------------------------
	// create function's package-based dictionary index
	lTemp = lPkgIndex + 1;
	sDictIndex = lTemp.toString(10);

	// function's name, $name, short name, description and class name
	window.external.AddSymbol("FUNCPACKAGE_FUNCNAME_" + sDictIndex, oFunc.name);
	window.external.AddSymbol("FUNCPACKAGE_DSFUNCNAME_" + sDictIndex, "$" + oFunc.name);
	window.external.AddSymbol("FUNCPACKAGE_SHORTNAME_" + sDictIndex, sShortName);
	window.external.AddSymbol("FUNCPACKAGE_FUNCDESC_" + sDictIndex, oFunc.description);
	window.external.AddSymbol("FUNCPACKAGE_CLASSNAME_" + sDictIndex, sClassName);
	if (oFunc.bRFunc)
	    window.external.AddSymbol("FUNCPACKAGE_RFUNCFLAG_" + sDictIndex, "true");
	else
	    window.external.AddSymbol("FUNCPACKAGE_RFUNCFLAG_" + sDictIndex, "false");

	// function's object and SQL types
	sTemp = aDSSFuncTypes[oFunc.mode].toString(10);
	window.external.AddSymbol("FUNCPACKAGE_OBJTYPE_" + sDictIndex, sTemp);
	if(oFunc.mode==fmTable)
	{
	    // need to check output parameter type
	    if(bNGenVectorOut)
	        // vector output
	        sTemp = DssFunctionSQLTypeRelative.toString(10);
	    else
	    {
	        if(bNGenVectorInput)
	            // scalar output with at least one vector input
	            sTemp = DssFunctionSQLTypeAggregation.toString(10);
	        else
	            // all scalar input and output
	            sTemp = DssFunctionSQLTypeArithmetic.toString(10);
	    }
	}
	else
	    // set SQL type based on function mode
	    sTemp = aSQLTypes[oFunc.mode].toString(10);
	window.external.AddSymbol("FUNCPACKAGE_SQLTYPE_" + sDictIndex, sTemp);

	// function's object-based index
	window.external.AddSymbol("FUNCOBJECT_FUNCINDEX_" + sDictIndex, sCount);

	// function's package-based index
	lTemp = lPkgIndex + 1;
	sCount = lTemp.toString(10);
	window.external.AddSymbol("FUNCPACKAGE_FUNCINDEX_" + sDictIndex, sCount);

	// function's in/out parameter counts
	sTemp = lInCnt.toString(10);
	window.external.AddSymbol("FUNCPACKAGE_INCNT_" + sDictIndex, sTemp);
	sTemp = lOutCnt.toString(10);
	window.external.AddSymbol("FUNCPACKAGE_OUTCNT_" + sDictIndex, sTemp);
	sTemp = oFunc.lRepCnt.toString(10);
	window.external.AddSymbol("FUNCPACKAGE_REPCNT_" + sDictIndex, sTemp);
	
	// function's IsOrderSig setting
	window.external.AddSymbol("FUNCPACKAGE_ISORDERSIG_" + sDictIndex, aBoolVals[oFunc.isOrderSig]);

	// PDS file code strings
	window.external.AddSymbol("FUNCPACKAGE_INPARAMTYPE_PDS_" + sDictIndex, sInParamTypesPds);
	window.external.AddSymbol("FUNCPACKAGE_OUTPARAMTYPE_PDS_" + sDictIndex, sOutParamTypesPds);
	window.external.AddSymbol("FUNCPACKAGE_INDATATYPE_PDS_" + sDictIndex, sInDataTypesPds);
	window.external.AddSymbol("FUNCPACKAGE_OUTDATATYPE_PDS_" + sDictIndex, sOutDataTypesPds);
	if (sPropsPds.length)
		// at least one property...add code string to dictionary
		window.external.AddSymbol("FUNCPACKAGE_PROPERTY_PDS_" + sDictIndex, sPropsPds);
	
    return true;
}

/*--------------------------------------------------------------------------------------
FUNCTION:   ProcessFuncs()
INPUTS:     none
RETURNS:    true            no errors during processing
            false           processing terminated due to error
CREATED:    10/18/07

This function process all functions defined by the Wizard.  Package-level symbols are added
to the dictionary, followed by symbols for each function.
--------------------------------------------------------------------------------------*/
function ProcessFuncs()
{
    var sProjectName;
    var sShortName;
    var sInterface;
    var sLibrary_IID;
    var sPkgCoClass_IID;
    var sPkgDefInt_IID;
    var sPackage_APPID;
    var sCount;
	var sGetFuncNames = "";
	var sBuffer = new String("");
    var today = new Date;
    var oFunc;
	var lSimpleCnt = 0;
	var lNNSimpleCnt = 0;
	var lGenericCnt = 0;
	var lNNGenericCnt = 0;
	var lAggregationCnt = 0;
	var lRFuncCnt = 0;
	var bGenRepFlag = false;
	var bNNGenRepFlag = false;
	var lIndex;
    
    // check library description
    if(txbLibDesc.value == "")
    {
        // must provide a library description
        window.alert(ERR_MISSING_LIB_DESC);
        return false;
    }
    // check for invalid characters
    lIndex = FindOneOf(INVALID_LIBDESC_CHARS, txbLibDesc.value, false);
    if (lIndex >= 0) {
        // library description is invalid
        window.alert(ERR_INVALID_LIB_DESC + INVALID_LIBDESC_CHARS);
        return false;
    }

    // check func count
    if(aFunctions.length == 0)
    {
        // must define at least one function
        window.alert(ERR_INVALID_FUNC_CNT);
        return false;
    }
    
    // access project name
    sProjectName = window.external.FindSymbol("PROJECT_NAME");
    
    // create package names
    g_sSafeRoot = CreateSafeName(sProjectName);
    sShortName = g_sSafeRoot + SFX_PACKAGE;
    sInterface = "I" + sShortName;

	// generate IID's for library, function package object coclass and interface
	sLibrary_IID = window.external.FormatGuid(window.external.CreateGuid(), 0);
	sPkgCoClass_IID = window.external.FormatGuid(window.external.CreateGuid(), 0);
	sPkgDefInt_IID = window.external.FormatGuid(window.external.CreateGuid(), 0);
	sPackage_APPID = window.external.FormatGuid(window.external.CreateGuid(), 0);
	
	// add a second symbol for PKG GUID, with hyphens removed (needed in get_CLSIDString() method)
	sPkgCoClass_IID_NH = sPkgCoClass_IID.substr(0, 8) + sPkgCoClass_IID.substr(9, 4) +
	    sPkgCoClass_IID.substr(14, 4) + sPkgCoClass_IID.substr(19, 4) + sPkgCoClass_IID.substr(24, 12);
	
    // add package symbols
    window.external.AddSymbol("LIBRARY_IID", sLibrary_IID);
    window.external.AddSymbol("root", sProjectName);
    window.external.AddSymbol("safe_root", g_sSafeRoot);
    window.external.AddSymbol("SAFE_ROOT", g_sSafeRoot.toUpperCase());
    window.external.AddSymbol("FUNCPACKAGE_COCLASS_IID", sPkgCoClass_IID);
    window.external.AddSymbol("FUNCPACKAGE_COCLASS_IID_NH", sPkgCoClass_IID_NH);
    window.external.AddSymbol("FUNCPACKAGE_DEFINTERFACE_IID", sPkgDefInt_IID);
    window.external.AddSymbol("FUNCPACKAGE_APPID", sPackage_APPID);
	window.external.AddSymbol("FUNCPACKAGE_SHORTNAME", sShortName);
	window.external.AddSymbol("FUNCPACKAGE_IDNAME", "@" + sShortName + "Package");
	window.external.AddSymbol("FUNCPACKAGE_DSIDNAME", "$" + sShortName + "Package");
	window.external.AddSymbol("FUNCPACKAGE_CLASSNAME", "C" + sShortName);
	window.external.AddSymbol("FUNCPACKAGE_COCLASS", sShortName);
	window.external.AddSymbol("FUNCPACKAGE_COMTYPE", sShortName + " class");
	window.external.AddSymbol("FUNCPACKAGE_CPPFILE", sShortName + ".cpp");
	window.external.AddSymbol("FUNCPACKAGE_HFILE", sShortName + ".h");
	window.external.AddSymbol("FUNCPACKAGE_INTERFACE", sInterface);
	window.external.AddSymbol("FUNCPACKAGE_VIPROGID", g_sSafeRoot + "." + sShortName);
	window.external.AddSymbol("FUNCPACKAGE_PROGID", g_sSafeRoot + "." + sShortName + ".1");
	window.external.AddSymbol("FUNCPACKAGE_CLSIDNAME", "CLSID_" + sShortName);
	window.external.AddSymbol("FUNCPACKAGE_DEFIIDNAME", "IID_" + sInterface);
	window.external.AddSymbol("FUNCPACKAGE_DESC", txbLibDesc.value);

	// add date and year symbols
	window.external.AddSymbol("FPDATE", aDays[today.getDay()] + ", " + aMonths[today.getMonth()] +
        " " + today.getDate() + ", " + today.getFullYear());
    window.external.AddSymbol("FPYEAR", today.getFullYear());
        
    // add count symbols
	window.external.AddSymbol("FUNCSIM_COUNT", aFPCounts[ftSimple]);
	window.external.AddSymbol("FUNCNNSIM_COUNT", aFPCounts[ftNNSimple]);
	window.external.AddSymbol("FUNCGEN_COUNT", aFPCounts[ftGeneric]);
	window.external.AddSymbol("FUNCNNGEN_COUNT", aFPCounts[ftNNGeneric]);
	window.external.AddSymbol("FUNCAGG_COUNT", aFPCounts[ftAggregation]);
	window.external.AddSymbol("FUNCPACKAGE_NONNUMERIC", aFPCounts[ftNNSimple] + aFPCounts[ftNNGeneric]);
	
	// add class name symbols
	if(aFPCounts[ftSimple] > 0)
	    window.external.AddSymbol("FUNCSIM_CLASSNAME", "C" + g_sSafeRoot + aObjAbbrs[ftSimple]);
	if(aFPCounts[ftNNSimple] > 0)
	    window.external.AddSymbol("FUNCNNSIM_CLASSNAME", "C" + g_sSafeRoot + aObjAbbrs[ftNNSimple]);
	if(aFPCounts[ftGeneric] > 0)
	    window.external.AddSymbol("FUNCGEN_CLASSNAME", "C" + g_sSafeRoot + aObjAbbrs[ftGeneric]);
	if(aFPCounts[ftNNGeneric] > 0)
	    window.external.AddSymbol("FUNCNNGEN_CLASSNAME", "C" + g_sSafeRoot + aObjAbbrs[ftNNGeneric]);
	if(aFPCounts[ftAggregation] > 0)
	    window.external.AddSymbol("FUNCAGG_CLASSNAME", "C" + g_sSafeRoot + aObjAbbrs[ftAggregation]);
	
	// process each function, based on type
	for(var i=0;i<aFunctions.length;i++)
	{
	    // access func
	    oFunc = aFunctions[i];
	    
	    // check for R function
	    if(oFunc.bRFunc)
	        lRFuncCnt++;
	    
	    switch(oFunc.fpType)
	    {
	        case ftSimple:
				if(!ProcessFunc(oFunc, lSimpleCnt, i))
					return false;

				// increment count
				lSimpleCnt++;
	            break;
	        case ftNNSimple:
				if(!ProcessFunc(oFunc, lNNSimpleCnt, i))
					return false;

				// increment count
				lNNSimpleCnt++;
	            break;
	        case ftGeneric:
				if(!ProcessFunc(oFunc, lGenericCnt, i))
					return false;
	    
	            if(oFunc.lRepCnt > 0)
	                // set flag to indicate at least one GEN function used 'repeat parameter' option
	                bGenRepFlag = true;

				// increment count
				lGenericCnt++;
	            break;
	        case ftNNGeneric:
				if(!ProcessFunc(oFunc, lNNGenericCnt, i))
					return false;
	    
	            if(oFunc.lRepCnt > 0)
	                // set flag to indicate at least one NNGEN function used 'repeat parameter' option
	                bNNGenRepFlag = true;

				// increment count
				lNNGenericCnt++;
	            break;
	        case ftAggregation:
				if(!ProcessFunc(oFunc, lAggregationCnt, i))
					return false;

				// increment count
				lAggregationCnt++;
	            break;
	        default:
	            window.alert(ERR_UNKNOWN_MODE_TYPE);
	            return false;
	    }
	    
		// increment property count
	    aPropCnts[oFunc.fpType] += oFunc.props.length;
	}

	// add symbols for 'properties exist' flags
	if(aPropCnts[ftSimple])
		window.external.AddSymbol("FUNCSIM_PROPS_EXIST", "TRUE")
	if(aPropCnts[ftNNSimple])
		window.external.AddSymbol("FUNCNNSIM_PROPS_EXIST", "TRUE");
	if(aPropCnts[ftGeneric])
		window.external.AddSymbol("FUNCGEN_PROPS_EXIST", "TRUE");
	if(aPropCnts[ftNNGeneric])
		window.external.AddSymbol("FUNCNNGEN_PROPS_EXIST", "TRUE");
	if(aPropCnts[ftAggregation])
		window.external.AddSymbol("FUNCAGG_PROPS_EXIST", "TRUE");
	
	// add symbol for package function count
	sCount = aFunctions.length.toString(10);
	window.external.AddSymbol("FUNCPACKAGE_COUNT", sCount);
	
	// add symbols for repeat flag, if necessary
	if(bGenRepFlag)
		window.external.AddSymbol("FUNCGEN_REPEAT_FLAG", "TRUE")
	if(bNNGenRepFlag)
		window.external.AddSymbol("FUNCNNGEN_REPEAT_FLAG", "TRUE")
	
	// R support
	if(lRFuncCnt > 0)
	{
	    window.external.AddSymbol("RFUNCS_DEFINED", "TRUE");  // indicates R functions were defined in the package
	    window.external.AddSymbol("R_INCLS_LOCATION", g_sLocOfRIncls);
	}
	
	// add directive symbols (used by symbols within loops)
	window.external.AddSymbol("OUTPUT_DIRECTIVE", "[!output");
	window.external.AddSymbol("IF_DIRECTIVE", "[!if");
	window.external.AddSymbol("ELSE_DIRECTIVE", "[!else]");
	window.external.AddSymbol("ENDIF_DIRECTIVE", "[!endif]");
	
	// just in case 'Finish' was clicked, add default entries for ATL support
	// specify ATL support
	window.external.AddSymbol("ATL_SUPPORT", "TRUE");
	window.external.AddSymbol("ATL_THREAD_BOTH", "TRUE");
	window.external.AddSymbol("ATL_INTERFACE_DUAL", "TRUE");
	window.external.AddSymbol("ATL_AGGREGATION_YES", "TRUE");
	
	// add in-proc symbols (out-of-proc not supported)
	window.external.AddSymbol("ATL_SERVERTYPE_DLL", "TRUE");
	window.external.AddSymbol("PROJTYPE_DLL", "TRUE");
	
	return true;
}

/*--------------------------------------------------------------------------------------
FUNCTION:   LoadFuncFromSyms()
INPUTS:     lIndex          0-based function index, based on function type
            sObjAbbr        abbreviation of function type
RETURNS:    true            no errors during processing
            false           processing terminated due to error
CREATED:    8/28/08

This function loads the function specified by the function index and abbreviation.
--------------------------------------------------------------------------------------*/
function LoadFuncFromSyms(lIndex, sObjAbbr)
{
    var i;
    var oFunc;
    var oParam;
    var oProp;
    var sFIndex;    // function index
    var sPIndex;    // parameter/property index
    var lParamCnt;
    var lPropCnt;
    
    // initialize
    sFIndex = lIndex.toString(10);
    lParamCnt = window.external.FindSymbol("FUNC" + sObjAbbr + "_PARAMCNT_" + sFIndex);
    lPropCnt = window.external.FindSymbol("FUNC" + sObjAbbr + "_PROPCNT_" + sFIndex);
    
    // allocate and initialize new function object
    oFunc = new Object;
    oFunc.name = window.external.FindSymbol("FUNC" + sObjAbbr + "_FUNCNAME_" + sFIndex);
    oFunc.description = window.external.FindSymbol("FUNC" + sObjAbbr + "_FUNCDESC_" + sFIndex);
    oFunc.mode = window.external.FindSymbol("FUNC" + sObjAbbr + "_MODE_" + sFIndex);
    oFunc.fpType = window.external.FindSymbol("FUNC" + sObjAbbr + "_FPTYPE_" + sFIndex);
    oFunc.lRepCnt = window.external.FindSymbol("FUNC" + sObjAbbr + "_REPCNT_" + sFIndex);
    oFunc.isOrderSig = window.external.FindSymbol("FUNC" + sObjAbbr + "_ISORDERSIG_" + sFIndex);
    oFunc.params = new Array();
    oFunc.props = new Array();
    
    // process parameters
    for(i=0;i<lParamCnt;i++)
    {
        // allocate and initialize new parameter object
        oParam = new Object;
        sPIndex = i.toString(10);
        oParam.name = window.external.FindSymbol("FUNC" + sObjAbbr + "_PARAMNAME_" + sFIndex + "_" + sPIndex);
        oParam.paramType = window.external.FindSymbol("FUNC" + sObjAbbr + "_PARAMTYPE_" + sFIndex + "_" + sPIndex);
        oParam.dataType = window.external.FindSymbol("FUNC" + sObjAbbr + "_DATATYPE_" + sFIndex + "_" + sPIndex);
        oParam.ioType = window.external.FindSymbol("FUNC" + sObjAbbr + "_IOTYPE_" + sFIndex + "_" + sPIndex);
        
        // add new parameter object to list
        oFunc.params.push(oParam);
    }
    
    // process properties
    for(i=0;i<lPropCnt;i++)
    {
        // allocate and initialize new property object
        oProp = new Object;
        sPIndex = i.toString(10);
        oProp.name = window.external.FindSymbol("FUNC" + sObjAbbr + "_PROPNAME_" + sFIndex + "_" + sPIndex);
        oProp.propType = window.external.FindSymbol("FUNC" + sObjAbbr + "_PROPTYPE_" + sFIndex + "_" + sPIndex);
        oProp.defValue = window.external.FindSymbol("FUNC" + sObjAbbr + "_DEFVALUE_" + sFIndex + "_" + sPIndex);
        oProp.description = window.external.FindSymbol("FUNC" + sObjAbbr + "_PROPDESC_" + sFIndex + "_" + sPIndex);
        
        // add new property object to list
        oFunc.props.push(oProp);
    }
    
    // add new function object to end of functions array
    aFunctions[aFunctions.length] = oFunc;
}

/*--------------------------------------------------------------------------------------
FUNCTION:   LoadFromSymbols()
INPUTS:     none
RETURNS:    true            no errors during processing
            false           processing terminated due to error
CREATED:    8/26/08

This function loads the aFunctions array with functions defined in the symbol table.  Other
than the first time default.htm is loaded, this method will be called by InitDocument() to
load any existing functions into the aFunctions array.
--------------------------------------------------------------------------------------*/
function LoadFromSymbols()
{
    var i;
	var lSimpleCnt;
	var lNNSimpleCnt;
	var lGenericCnt;
	var lNNGenericCnt;
	var lAggregationCnt;
    
    // get total function counts from symbol table
    lSimpleCnt = window.external.FindSymbol("FUNCSIM_COUNT");
    lNNSimpleCnt = window.external.FindSymbol("FUNCNNSIM_COUNT");
    lGenericCnt = window.external.FindSymbol("FUNCGEN_COUNT");
    lNNGenericCnt = window.external.FindSymbol("FUNCNNGEN_COUNT");
    lAggregationCnt = window.external.FindSymbol("FUNCAGG_COUNT");

    // process each Simple function
    for(i=0;i<lSimpleCnt;i++)
    {
        LoadFuncFromSyms(i + 1, aObjAbbrs[ftSimple]);
        aFPCounts[ftSimple]++;
    }
    
    // process each NNSimple function
    for(i=0;i<lNNSimpleCnt;i++)
    {
        LoadFuncFromSyms(i + 1, aObjAbbrs[ftNNSimple]);
        aFPCounts[ftNNSimple]++;
    }
    
    // process each Generic function
    for(i=0;i<lGenericCnt;i++)
    {
        LoadFuncFromSyms(i + 1, aObjAbbrs[ftGeneric]);
        aFPCounts[ftGeneric]++;
    }
    
    // process each NNGeneric function
    for(i=0;i<lNNGenericCnt;i++)
    {
        LoadFuncFromSyms(i + 1, aObjAbbrs[ftNNGeneric]);
        aFPCounts[ftNNGeneric]++;
    }
    
    // process each Aggregation function
    for(i=0;i<lAggregationCnt;i++)
    {
        LoadFuncFromSyms(i + 1, aObjAbbrs[ftAggregation]);
        aFPCounts[ftAggregation]++;
    }
}

/*--------------------------------------------------------------------------------------
FUNCTION:   ProcessATLOptions()
INPUTS:     none
RETURNS:    true            no errors during processing
            false           processing terminated due to error
CREATED:    12/3/07

This function process the ATL options.
--------------------------------------------------------------------------------------*/
function ProcessATLOptions()
{
    if(eThreadModel == tmFree)
        // remove symbol for 'both' (default)...no thread model symbol will utilize 'free'
        window.external.RemoveSymbol("ATL_THREAD_BOTH");
    
    if(eAggregation != agYes)
    {
        // remove symbol for 'yes' (default)
        window.external.RemoveSymbol("ATL_AGGREGATION_YES");
        
        if(eAggregation == agNo)
            // add symbol for 'no'
	        window.external.AddSymbol("ATL_AGGREGATION_NO", "TRUE");
	    else
	        // must be 'only'...add corresponding symbol
	        window.external.AddSymbol("ATL_AGGREGATION_ONLY", "TRUE");
    }
    
    if(bSuppIErrInfo)
	    window.external.AddSymbol("ATL_SUPPORT_ERRORINFO", "TRUE");
	if(bSuppConnPts)
	    window.external.AddSymbol("ATL_SUPPORT_CONNPTS", "TRUE");
	if(bFreeThrdMrsh)
	    window.external.AddSymbol("ATL_FTM", "TRUE");
}
